﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Diagnostics;
using DavuxLib;

namespace CamShared
{
    public class HTTPClient
    {
        public delegate void RequestHandler(HTTPClient client, string url, string method, string body);
        public event RequestHandler OnRequest;

        private TcpClient _client = null;
        public List<KeyValuePair<string, string>> Headers = new List<KeyValuePair<string, string>>();
        private string _Url = "";
        private string _Method = "";
        private string _httpVersion = "";
        private string _Body = "";

        public HTTPClient(TcpClient client)
        {
            _client = client;
        }

        public void ReadRequest()
        {
            init:
            ReadHeaders();

            if (Settings.Get("Simple Webserver|Keep-Alive Enabled", true))
            {
                if (GetHeader("connection").ToLower() == "keep-alive")
                {
                    Headers.Clear();
                    //Trace.Write("(Keep-Alive)");
                    goto init;
                }
            }
            Close();
        }

        public Socket Socket
        {
            get
            {
                return _client.Client;
            }
        }

        public TcpClient Client
        {
            get
            {
                return _client;
            }
        }

        public string GetMimeTypeFromFile(string fileName)
        {
            string mimeType = "application/unknown";
            string ext = System.IO.Path.GetExtension(fileName).ToLower();
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
            if (regKey != null && regKey.GetValue("Content Type") != null)
            {
                mimeType = regKey.GetValue("Content Type").ToString();
            }

            // reg reads won't work on mono.
            if (fileName.EndsWith(".html") || fileName.EndsWith(".htm"))
            {
                mimeType = "text/html";
            }
            if (fileName.EndsWith(".js"))
            {
                mimeType = "text/javascript";
            }
            if (fileName.EndsWith(".css"))
            {
                mimeType = "text/css";
            }

            return mimeType;
        }



        private void ReadHeaders()
        {
            try
            {
                StreamReader sr = new StreamReader(_client.GetStream());
                string requestLine = sr.ReadLine();
                //Trace.WriteLine("Request: " + requestLine);
                if (requestLine == null)
                {
                    //Trace.WriteLine("Empty Request");
                    return;
                }
                string[] rlParts = requestLine.Split(new char[] { ' ' });

                if (rlParts.Length == 3)
                {
                    _Method = rlParts[0];
                    _Url = rlParts[1];
                    _httpVersion = rlParts[2];

                    if (_httpVersion == "HTTP/1.1")
                    {
                        switch (_Method.ToUpper())
                        {
                            case "GET":

                                // continue.
                                // read rest of request headers

                                bool readingHeaders = true;

                                while (readingHeaders)
                                {
                                    string h = sr.ReadLine();
                                    if (h != "")
                                    {
                                        ReadHeader(h);
                                    }
                                    else
                                    {
                                        readingHeaders = false;
                                    }
                                }

                                int cl = GetIntHeader("Content-Length");
                                if (cl > 0)
                                {
                                    char[] buf = new char[cl];
                                    sr.ReadBlock(buf, 0, cl);
                                    _Body = new string(buf);
                                }
                                //Trace.WriteLine("HTTP " + Client.Client.RemoteEndPoint.ToString() + " : " + _Url);
                                if (OnRequest != null)
                                {
                                    
                                    OnRequest.Invoke(this, _Url, _Method, _Body);
                                }
                                return;
                               //  break;

                        }
                    }
                    else
                    {
                        Trace.WriteLine("Not Responding to HTTP 1.0 client " + Client.Client.RemoteEndPoint + " " + _Url);
                    }
                }
                Trace.WriteLine("Bad Request");
                SendResponseHeader("400 Bad Request");
                EndHeader();
                Close();
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error Reading Headers: " + ex);
            }
        }


        public string GetHeader(string key)
        {
            key = key.ToLower();
            foreach (KeyValuePair<string, string> kv in Headers)
            {
                if (kv.Key == key)
                {
                    return kv.Value;
                }
            }
            return "";
        }

        public int GetIntHeader(string key)
        {
            int iOut;
            return int.TryParse(GetHeader(key), out iOut) ? iOut : 0;
        }

        private void ReadHeader(string header)
        {
            int c = header.IndexOf(':');
            if (c > -1)
            {
                string key = header.Substring(0, c).ToLower();
                string value = header.Substring(c + 1).Trim();
                Headers.Add(new KeyValuePair<string,string>(key, value));
               // Trace.WriteLine("Add Header: " + key);
            }
        }

        public void SendResponseHeader(string header)
        {
            StreamWriter sw = new StreamWriter(_client.GetStream());
            sw.WriteLine("HTTP/1.1 " + header);
            sw.Flush();
        }

        public void SendHeader(string header)
        {
            //Trace.WriteLine("Add header: " + header);
            StreamWriter sw = new StreamWriter(_client.GetStream());
            sw.WriteLine(header);
            sw.Flush();
        }

        public void EndHeader()
        {
            AddConnectionHeader();
            StreamWriter sw = new StreamWriter(_client.GetStream());
            sw.WriteLine();
            sw.Flush();
        }



        public void SendPage(string page)
        {
            MemoryStream ms = new MemoryStream();
            StreamWriter sw = new StreamWriter(ms);
            sw.Write(page);
            sw.Flush();


            SendResponseHeader("200 OK");
            SendHeader("X-Powered-By: DavuxWeb/1.0");
            SendHeader("Content-Length: " + ms.Length);
            EndHeader();
            _client.GetStream().Write(ms.ToArray(), 0, (int)ms.Length);

        }

        private void AddConnectionHeader()
        {
            if (Settings.Get("Simple Webserver|Keep-Alive Enabled", true) && GetHeader("connection").ToLower() == "keep-alive")
            {
                SendHeader("Connection: Keep-Alive");
            }
            else
            {
                SendHeader("Connection: Close");
            }
        }

        private void Close()
        {
            _client.Close();
        }

        public bool AuthorizationCheck(string user, string pw)
        {
            string auth = GetHeader("authorization");

            if (auth.IndexOf(' ') > -1)
            {
                string[] p = auth.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (p.Length == 2)
                {
                    if (p[0].ToLower() == "basic")
                    {
                        if (p[1] == B64Encode(user + ":" + pw))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }



        public static string B64Encode(string str)
        {
            byte[] encbuff = System.Text.Encoding.UTF8.GetBytes(str);
            return Convert.ToBase64String(encbuff);
        }
        public static string B64Decode(string str)
        {
            byte[] decbuff = Convert.FromBase64String(str);
            return System.Text.Encoding.UTF8.GetString(decbuff);
        }
    }
}
